package cn.lg.soar.common.util.signature;

import cn.lg.soar.common.algorithm.CryptoUtils;
import cn.lg.soar.common.util.asserts.InnerAssert;
import cn.lg.soar.common.util.data.DataUtil;
import cn.lg.soar.common.util.reflect.ReflectUtil;

import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 签名工具
 * @author luguoxiang
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public interface SignatureUtils {

    /**
     * 根据字符串生成签名
     * @param secret 密钥
     * @param data 要签名的数据
     * @return
     */
    static String sign(String secret, String data) {
        return DataUtil.byteToUrlBase64(
                CryptoUtils.sm3((secret + data).getBytes())
        );
    }

    /**
     * 根据map生成签名
     * @param secret 密钥
     * @param data 要签名的数据
     * @param ignoreFields 不参与签名的属性
     * @return
     */
    static String sign(String secret, Map<String, Object> data, String ...ignoreFields) {
        InnerAssert.notEmpty(data, "data cannot empty");
        Set<String> ignoreSet = Arrays.stream(ignoreFields).collect(Collectors.toSet());
        Set<Map.Entry<String, Object>> entries = data.entrySet();
        StringBuilder sb = new StringBuilder();
        entries.stream()
                .sorted(Utils.COMPARATOR)
                .forEach(x -> {
                    String key = x.getKey();
                    // 属性黑名单
                    if (ignoreSet.contains(key)) {
                        return;
                    }
                    sb.append(key)
                            .append('=')
                            .append(x.getValue())
                            .append('&');
                });
        return sign(secret, sb.substring(0, sb.length() - 1));
    }

    /**
     * 根据对象生成签名
     * @param secret 密钥
     * @param data 要签名的数据
     * @param ignoreFields 不参与签名的属性
     * @return
     */
    static String sign(String secret, Object data, String ...ignoreFields) {
        Set<String> ignoreSet = Arrays.stream(ignoreFields).collect(Collectors.toSet());
        Map<String, Object> map = new HashMap<>();
        ReflectUtil.scanField(data.getClass(), field -> {
            String name = field.getName();
            if (ignoreSet.contains(name) || field.getAnnotation(SignIgnore.class) != null || field.getAnnotation(SignValue.class) != null) {
                return;
            }
            // 生成参数
            map.put(name, ReflectUtil.get(data, field));
        });
        return sign(secret, map);
    }

    /**
     * 根据对象生成签名
     * @param secret 密钥
     * @param data 要签名的数据
     * @param includeFields 要参与签名的属性
     * @return
     */
    static String signInclude(String secret, Map<String, Object> data, String ...includeFields) {
        InnerAssert.notEmpty(includeFields, "includeFields cannot empty");
        InnerAssert.notEmpty(data, "data cannot empty");
        Set<String> includeSet = Arrays.stream(includeFields).collect(Collectors.toSet());
        Set<Map.Entry<String, Object>> entries = data.entrySet();
        StringBuilder sb = new StringBuilder();
        entries.stream()
                .sorted(Utils.COMPARATOR)
                .forEach(x -> {
                    String key = x.getKey();
                    // 属性黑名单
                    if (includeSet.contains(key)) {
                        sb.append(key)
                                .append('=')
                                .append(x.getValue())
                                .append('&');
                    }
                });
        return sign(secret, sb.substring(0, sb.length() - 1));
    }

    /**
     * 根据对象生成签名
     * @param secret 密钥
     * @param data 要签名的数据
     * @param includeFields 要参与签名的属性
     * @return
     */
    static String signInclude(String secret, Object data, String ...includeFields) {
        InnerAssert.notEmpty(includeFields, "includeFields cannot empty");
        Set<String> includeSet = Arrays.stream(includeFields).collect(Collectors.toSet());
        Map<String, Object> map = new HashMap<>();
        ReflectUtil.scanField(data.getClass(), field -> {
            String name = field.getName();
            if (includeSet.contains(name) || field.getAnnotation(SignField.class) != null) {
                // 生成参数
                map.put(name, ReflectUtil.get(data, field));
            }
        });
        return sign(secret, map);
    }

    /**
     * 验签
     * @param sign 签名值
     * @param secret 密钥
     * @param data 原始数据
     * @return
     */
    static boolean verify(String sign, String secret, String data) {
        return sign(secret, data).equals(sign);
    }

    /**
     * 验签
     * @param sign 签名值
     * @param secret 密钥
     * @param data 原始数据
     * @param ignoreFields 不参与签名的属性
     * @return
     */
    static boolean verify(String sign, String secret, Map<String, Object> data, String ...ignoreFields) {
        return sign(secret, data, ignoreFields).equals(sign);
    }

    /**
     * 验签
     * @param sign 签名值
     * @param secret 密钥
     * @param data 原始数据
     * @param ignoreFields 不参与签名的属性
     * @return
     */
    static boolean verify(String sign, String secret, Object data, String ...ignoreFields)  {
        return sign(secret, data, ignoreFields).equals(sign);
    }

    /**
     * 验签
     * @param sign 签名值
     * @param secret 密钥
     * @param data 原始数据
     * @param includeFields 要参与签名的属性
     * @return
     */
    static boolean verifyInclude(String sign, String secret, Map<String, Object> data, String ...includeFields)  {
        return signInclude(secret, data, includeFields).equals(sign);
    }

    /**
     * 验签
     * @param sign 签名值
     * @param secret 密钥
     * @param data 原始数据
     * @param includeFields 要参与签名的属性
     * @return
     */
    static boolean verifyInclude(String sign, String secret, Object data, String ...includeFields)  {
        return signInclude(secret, data, includeFields).equals(sign);
    }

    /**
     * 创建有效期
     * @param duration 有效时长，单位：毫秒
     * @return
     */
    static long createExpire(long duration) {
        return duration + System.currentTimeMillis();
    }

    /**
     * 创建有效期
     * @param duration 有效时长
     * @return
     */
    static long createExpire(Duration duration) {
        return createExpire(duration.getSeconds() * 1000);
    }

    /**
     * 验证有效期
     * @param expire
     * @return
     */
    static boolean verifyExpire(long expire) {
        return expire > System.currentTimeMillis();
    }

}
